contents
Pair라는 개념은 매우 흔하지만, 제네릭 Pair 클래스는 자바의 표준 라이브러리(JDK)에 포함되어 있지 않습니다. 이는 종종 개발자들을 놀라게 하는 중요한 사실입니다. 대신, Pair는 개발자가 직접 만들거나 서드파티 라이브러리를 통해 사용하는, 두 개의 객체를 함께 담는 간단한 자료구조입니다.
Pair의 주된 목적은 자바에서 기본적으로 불가능한, 메서드에서 두 개의 값을 반환하는 편리한 방법을 제공하는 것입니다.
Pair 클래스가 해결하는 문제
자바 메서드는 단 하나의 값 또는 객체만 반환할 수 있습니다. 만약 한 번에 두 가지를 반환해야 한다면 어떻게 해야 할까요? 예를 들어, 배열에서 최솟값과 최댓값을 모두 찾는 메서드가 필요할 수 있습니다.
Pair 패턴 이전에는 개발자들이 다음과 같이 이상적이지 않은 해결책을 사용했습니다.
Object[]또는List반환: 타입 세이프하지 않고(요소를 원래 타입으로 다시 형변환해야 함), 의미가 부족합니다(인덱스 0의 요소가 무엇을 의미하는지 알기 어려움).- 특정 클래스 생성: 이 메서드 하나만을 위해
MinMaxResult와 같은 새 클래스를 매번 만들 수 있습니다. 이는 타입 세이프하지만 매우 장황하고 많은 상용구 코드를 유발합니다.
Pair 클래스는 제네릭을 사용하여 재사용 가능한 해결책을 제공합니다. 이는 간단한 두 요소 컨테이너 역할을 하여, 매번 특정 클래스를 새로 만들지 않고도 어떤 두 객체든 단일 단위로 묶어 반환할 수 있게 해줍니다.
나만의 Pair 클래스 만들기 📦
표준 라이브러리에 없기 때문에, 개발자나 프로젝트가 자신만의 Pair 구현을 갖는 것은 매우 일반적입니다. 좋은 최신 Pair 클래스는 제네릭을 사용하며 불변(immutable)입니다.
다음은 잘 설계된 완전한 예제입니다.
import java.util.Objects;
// 왼쪽(L)과 오른쪽(R) 타입에 제네릭을 사용합니다.
public final class Pair {
private final L left;
private final R right;
// 정적 팩토리 메서드를 통한 생성을 강제하기 위해 private 생성자 사용
private Pair(L left, R right) {
this.left = left;
this.right = right;
}
// 편리한 생성을 위한 정적 팩토리 메서드
public static Pair of(L left, R right) {
return new Pair<>(left, right);
}
// 값에 대한 getter
public L getLeft() {
return left;
}
public R getRight() {
return right;
}
// 컬렉션에서 올바르게 동작하도록 equals()와 hashCode() 재정의
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair, ?> pair = (Pair, ?>) o;
return Objects.equals(left, pair.left) && Objects.equals(right, pair.right);
}
@Override
public int hashCode() {
return Objects.hash(left, right);
}
// 쉬운 디버깅을 위해 toString() 재정의
@Override
public String toString() {
return "Pair{" +
"left=" + left +
", right=" + right +
'}';
}
}
Pair 클래스 사용 방법
위 클래스를 사용하여, 이제 최솟값/최댓값 문제를 깔끔하게 해결할 수 있습니다.
public class ArrayUtils {
public static Pair findMinMax(int[] numbers) {
if (numbers == null || numbers.length == 0) {
throw new IllegalArgumentException("배열은 비어 있을 수 없습니다.");
}
int min = numbers[0];
int max = numbers[0];
for (int number : numbers) {
if (number < min) min = number;
if (number > max) max = number;
}
// 두 개의 값을 하나의 Pair 객체로 감싸서 반환
return Pair.of(min, max);
}
public static void main(String[] args) {
int[] data = {5, 1, 9, 3, 7, 2, 8};
Pair result = findMinMax(data);
System.out.println("최솟값: " + result.getLeft()); // 출력: 최솟값: 1
System.out.println("최댓값: " + result.getRight()); // 출력: 최댓값: 9
}
}
Pair 클래스를 찾을 수 있는 곳
직접 작성하고 싶지 않다면, 몇몇 인기 있는 라이브러리에서 자체 구현을 제공합니다.
- JavaFX:
javafx.util.Pair클래스는 일부 JDK 배포판에 포함된 JavaFX 라이브러리에 있습니다. - Apache Commons Lang: 널리 사용되는
org.apache.commons.lang3.tuple.Pair는 매우 일반적인 선택입니다. 이 라이브러리는Triple및 다른 튜플 유사 클래스도 제공합니다. - 스프링 프레임워크: 스프링 데이터 모듈에는
org.springframework.data.util.Pair클래스가 포함되어 있습니다.
현대적인 대안: 레코드 (자바 16 이상) ✨
많은 경우, "두 개의 값을 반환"하는 문제를 해결하는 현대적인 방법은 **레코드(record)**를 사용하는 것입니다. 레코드는 불변의 데이터 전달 클래스를 만들기 위한 간결한 문법입니다.
제네릭 Pair를 만드는 대신, 거의 상용구 코드 없이 의미가 명확한 특정 레코드를 만들 수 있습니다.
최솟값/최댓값 예제에 레코드 사용:
// 단 한 줄로 레코드를 정의!
public record MinMaxResult(int min, int max) {}
이 한 줄은 자동으로 다음과 같은 것들을 갖춘 클래스를 생성합니다.
min과max를 위한private final필드.public생성자.publicgetter와 유사한 메서드 (min()과max()).equals(),hashCode(),toString()의 적절한 구현.
사용법:
public static MinMaxResult findMinMaxWithRecord(int[] numbers) {
// ... 이전과 동일한 로직 ...
return new MinMaxResult(min, max);
}
// --- main 메서드 내 ---
MinMaxResult result = findMinMaxWithRecord(data);
System.out.println("최솟값: " + result.min());
System.out.println("최댓값: " + result.max());
제네릭 Pair 클래스도 여전히 유용한 도구이지만, 반환하는 두 값이 명확하고 구체적인 의미를 갖는다면, 레코드가 종종 더 깔끔하고, 표현력이 좋으며, 현대적인 자바 접근 방식입니다.
references